package com.points.system.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.points.system.domain.PointsOperationLog;
import com.points.system.domain.UserPoints;
import com.points.system.mapper.UserPointsMapper;
import com.points.system.service.PointsOperationLogService;
import com.points.system.service.UserPointsService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
@RequiredArgsConstructor
public class UserPointsServiceImpl implements UserPointsService {

    private final UserPointsMapper userPointsMapper;
    private final PointsOperationLogService pointsOperationLogService;
    private final RedisTemplate<String, Object> redisTemplate;

    private static final String POINTS_CACHE_KEY = "user:points:";
    private static final String POINTS_EXPIRE_QUEUE = "points:expire:queue";

    @Override
    public UserPoints getUserPoints(Long userId) {
        String cacheKey = POINTS_CACHE_KEY + userId;
        UserPoints userPoints = (UserPoints) redisTemplate.opsForValue().get(cacheKey);
        if (userPoints != null) {
            return userPoints;
        }

        userPoints = userPointsMapper.selectOne(
            new LambdaQueryWrapper<UserPoints>()
                .eq(UserPoints::getUserId, userId)
        );

        if (userPoints != null) {
            redisTemplate.opsForValue().set(cacheKey, userPoints, 1, TimeUnit.HOURS);
        }

        return userPoints;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void rechargePoints(Long userId, Long points, LocalDateTime expireTime) {
        UserPoints userPoints = getUserPoints(userId);
        if (userPoints == null) {
            userPoints = new UserPoints();
            userPoints.setUserId(userId);
            userPoints.setPoints(points);
            userPoints.setTotalPoints(points);
            userPoints.setExpireTime(expireTime);
            userPointsMapper.insert(userPoints);
        } else {
            Long beforePoints = userPoints.getPoints();
            userPoints.setPoints(beforePoints + points);
            userPoints.setTotalPoints(userPoints.getTotalPoints() + points);
            userPoints.setExpireTime(expireTime);
            userPointsMapper.updateById(userPoints);
        }

        // 记录操作日志
        PointsOperationLog log = new PointsOperationLog();
        log.setUserId(userId);
        log.setOperationType(PointsOperationLog.OPERATION_RECHARGE);
        log.setPoints(points);
        log.setBeforePoints(userPoints.getPoints() - points);
        log.setAfterPoints(userPoints.getPoints());
        log.setExpireTime(expireTime);
        log.setDescription("积分充值");
        pointsOperationLogService.recordLog(log);

        // 更新缓存
        String cacheKey = POINTS_CACHE_KEY + userId;
        redisTemplate.opsForValue().set(cacheKey, userPoints, 1, TimeUnit.HOURS);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void consumePoints(Long userId, Long points) {
        UserPoints userPoints = getUserPoints(userId);
        if (userPoints == null || userPoints.getPoints() < points) {
            throw new RuntimeException("积分不足");
        }

        Long beforePoints = userPoints.getPoints();
        userPoints.setPoints(beforePoints - points);
        userPoints.setUsedPoints(userPoints.getUsedPoints() + points);
        userPointsMapper.updateById(userPoints);

        // 记录操作日志
        PointsOperationLog log = new PointsOperationLog();
        log.setUserId(userId);
        log.setOperationType(PointsOperationLog.OPERATION_CONSUME);
        log.setPoints(points);
        log.setBeforePoints(beforePoints);
        log.setAfterPoints(userPoints.getPoints());
        log.setDescription("积分消费");
        pointsOperationLogService.recordLog(log);

        // 更新缓存
        String cacheKey = POINTS_CACHE_KEY + userId;
        redisTemplate.opsForValue().set(cacheKey, userPoints, 1, TimeUnit.HOURS);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchRechargePoints(List<Long> userIds, Long points, LocalDateTime expireTime) {
        for (Long userId : userIds) {
            rechargePoints(userId, points, expireTime);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchConsumePoints(List<Long> userIds, Long points) {
        for (Long userId : userIds) {
            consumePoints(userId, points);
        }
    }

    @Override
    // @PostConstruct
    @Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1点执行
    public void handleExpiredPoints() {
        List<UserPoints> expiredPointsList = userPointsMapper.selectList(
            new LambdaQueryWrapper<UserPoints>()
                .le(UserPoints::getExpireTime, LocalDateTime.now())
                .gt(UserPoints::getPoints, 0)
        );

        for (UserPoints userPoints : expiredPointsList) {
            Long expiredPoints = userPoints.getPoints();
            userPoints.setPoints(0L);
            userPoints.setExpiredPoints(userPoints.getExpiredPoints() + expiredPoints);
            userPointsMapper.updateById(userPoints);

            // 记录操作日志
            PointsOperationLog log = new PointsOperationLog();
            log.setUserId(userPoints.getUserId());
            log.setOperationType(PointsOperationLog.OPERATION_EXPIRE);
            log.setPoints(expiredPoints);
            log.setBeforePoints(expiredPoints);
            log.setAfterPoints(0L);
            log.setDescription("积分过期");
            pointsOperationLogService.recordLog(log);

            // 发送过期通知到Redis队列
            redisTemplate.opsForList().rightPush(POINTS_EXPIRE_QUEUE,
                String.format("用户%d的%d积分已过期", userPoints.getUserId(), expiredPoints));

            // 更新缓存
            String cacheKey = POINTS_CACHE_KEY + userPoints.getUserId();
            redisTemplate.opsForValue().set(cacheKey, userPoints, 1, TimeUnit.HOURS);
        }
    }
} 